package logic

import (
	syscontext "context"
	"errors"
	"github.com/dgrijalva/jwt-go"
	"github.com/spf13/viper"
	"take-a-break/app/models"
	modelDb "take-a-break/app/models/db"
	modelRedis "take-a-break/app/models/redis"
	redis2 "take-a-break/app/models/redis"
	"take-a-break/app/models/request"
	"take-a-break/component/redis"
	"take-a-break/exception"
	"time"

	"take-a-break/utils/common"

	"xorm.io/xorm"
)

// LoginLogic 回调通用结构体
type LoginLogic struct {
	ctx syscontext.Context
}

// NewLoginLogic ...
func NewLoginLogic(ctx syscontext.Context) *LoginLogic {
	return &LoginLogic{ctx: ctx}
}

// LoginWithWxa 通过小程序登录
func (l *LoginLogic) LoginWithWxa(session *xorm.Session, openID, unionID, sessionKey string, ip string) (token *models.TokenData, mUser *modelDb.User, err error) {
	//查询是否存在账号
	exists, mUser, err := modelDb.NewUser().GetWithWxaOpenID(l.ctx, session, openID)
	if err != nil { // 查询错误
		return
	}

	usingUnionID := false
	if !exists { // 当前不存在openid则检查unionid
		exists, mUser, err = modelDb.NewUser().GetWithUnionID(l.ctx, session, unionID)
		if err != nil {
			return
		}

		if !exists { // 当前不存在openid 并且不存在对应的unionid时。 抛出账号未注册的错误
			err = exception.AccountNotExistError
			return
		}
		usingUnionID = true
	}

	if mUser.Status != modelDb.NrUserStatusIsOpen {
		err = errors.New("当前账号已经被禁用")
		return
	}

	// 创建token并返回
	token, err = l.GenerateToken(mUser.UserSid, 0)
	if err != nil {
		err = exception.SystemError
		return
	}

	// 处理存在unionID的情况
	if len(mUser.UnionID) == 0 {
		mUser.UnionID = unionID
	}

	if usingUnionID { // 通过unionID查询到的用户信息，自动补全小程序和服务号openID
		// 1. 查询当前是否存在已关注的服务号
		var exists bool
		var mOfficialAccount *modelDb.WeixinOfficialAccount
		exists, mOfficialAccount, err = modelDb.NewWeixinOfficialAccount().GetByUnionID(l.ctx, session, unionID)
		if err != nil {
			return
		}
		if exists {
			// 2. 将所有的信息都填写到对应字段
			mUser.OaOpenID = mOfficialAccount.OpenID
		}
		mUser.WxaOpenID = openID
	}

	mUser.WxaSessionKey = sessionKey
	//更新最后登录时间
	mUser.LastIP = ip
	mUser.LastLoginTime = common.GetCurrentTime()
	_, err = mUser.Update(l.ctx, session, "last_login_time,last_ip,union_id,wxa_open_id,wxa_session_key,oa_open_id")
	return
}

// GenerateToken 生成token, 设定过期时间
func (l *LoginLogic) GenerateToken(uid string, roleID int) (result *models.TokenData, err error) {
	expireDay := 7 //7天过期
	if exception.RunMode == exception.DEV {
		expireDay = 30
	}
	token, expireTime, err := l.generateToken(uid, roleID, l.expireTime(expireDay))
	if err != nil {
		return
	}
	cKey := redis2.GetLoginTokenKey(uid, roleID)
	_, _ = redis.Redis().SetString(cKey, token, 86400*expireDay)
	result = &models.TokenData{
		UUID:       uid,
		RoleID:     roleID,
		Token:      token,
		ExpireTime: expireTime,
	}
	return
}

// generateToken 生成一个token, 操作人员ID
func (l *LoginLogic) generateToken(uid string, roleID int, expireTime int64) (string, int64, error) {
	var err error
	authCode := []byte(viper.GetString("jwt.authCode"))
	exists, user, err := modelDb.NewUser().GetWithUUID(l.ctx, nil, uid)
	if err != nil {
		return "", 0, err
	}

	if !exists {
		err = errors.New("没有找到该用户")
		return "", 0, err
	}

	if user.Status != modelDb.NrUserStatusIsOpen {
		err = errors.New("当前用户被禁用")
		return "", 0, err
	}

	claims := jwt.MapClaims{
		"id":          user.UserSid,
		"role_id":     roleID,
		"expire_time": expireTime,
		"iat":         time.Now().Unix(), //签发时间  可以确保每次登录得到的token不同
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	authToken, err := token.SignedString(authCode)
	return authToken, expireTime, err
}

// expireTime token的过期时间，默认当前时间的7天后的，凌晨5点
func (l *LoginLogic) expireTime(days int) int64 {
	if days <= 0 {
		days = 7
	}
	currentTime := time.Now()
	zeroTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(),
		5, 0, 0, 0, currentTime.Location())
	expireTime := zeroTime.AddDate(0, 0, days).Unix()
	return expireTime
}

// GetAccountDB 获取账号信息
func (l *LoginLogic) GetAccountDB(session *xorm.Session, req *request.WechatLoginReq, data modelRedis.WxaCodeInfo, ip string) (isAdd bool, user *modelDb.User, err error) {
	//查询是否存在账号
	exists, user, err := modelDb.NewUser().GetWithWxaOpenID(l.ctx, session, data.OpenID)
	if err != nil { // 查询错误
		return
	}
	if !exists { // 当前不存在openid则检查unionid
		exists, user, err = modelDb.NewUser().GetWithUnionID(l.ctx, session, data.UnionID)
		if err != nil {
			return
		}
		if !exists { // 当前不存在openid 并且不存在对应的unionid时。直接注册
			user = &modelDb.User{
				UserSid:       common.MakeUUID(),
				WxaOpenID:     data.OpenID,
				UnionID:       data.UnionID,
				WxaSessionKey: data.SessionKey,
				Status:        modelDb.NrUserStatusIsOpen,
				RegTime:       time.Now().Unix(),
				LastLoginTime: time.Now().Unix(),
				LastIP:        ip,
			}
			_, err = user.Add(l.ctx, session)
			if err != nil {
				return
			}
			isAdd = true
		}
	}

	return
}

// CreateAccountDB 创建账号
func (l *LoginLogic) CreateAccountDB(dbSession *xorm.Session, phone, password, name, ip string) (user *modelDb.User, err error) {
	//查询是否存在账号
	exists, obj, err := modelDb.NewUser().GetWithPhoneAndNormalStatus(l.ctx, dbSession, phone)
	if err != nil { //查询错误
		return
	}
	if exists {
		if obj.Status == modelDb.NrUserStatusIsOpen { //已开启
			err = exception.AccountExistsError
			return
		}
		if obj.Status == modelDb.NrUserStatusIsOff { //已禁用
			err = exception.AccountNotExistsError
			return
		}
	}

	user = &modelDb.User{
		UserSid:       common.MakeUUID(),
		Phone:         phone,
		NickName:      name,
		Status:        modelDb.NrUserStatusIsOpen,
		Email:         "",
		RegTime:       common.GetCurrentTime(),
		LastLoginTime: common.GetCurrentTime(),
		LastIP:        ip,
	}

	_, err = user.Add(l.ctx, nil)
	if err != nil {
		return
	}
	return
}

func (l *LoginLogic) GetInfoWithPhone(phone string) (exists bool, user *modelDb.User, err error) {
	return modelDb.NewUser().GetWithPhone(l.ctx, nil, phone)
}
func (l *LoginLogic) UpdateWxaInfo(session *xorm.Session, user *modelDb.User, openID, unionID, sessionKey string) (err error) {
	user.WxaOpenID = openID
	user.WxaSessionKey = sessionKey
	user.UnionID = unionID
	_, err = user.Update(l.ctx, session, "union_id,wxa_open_id,wxa_session_key")
	return
}
